package com.xnx3;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import com.xnx3.Lang;

import cn.zvo.http.Http.TrustAnyHostnameVerifier;
import cn.zvo.http.Http.TrustAnyTrustManager;

/**
 * 文件操作
 * @author 管雷鸣
 */
public class FileUtil {
	public final static String UTF8="UTF-8";
	public final static String GBK="GBK";
	
	static final String ENCODE="UTF-8";	//默认文件编码UTF-8
	
	/**
	 * 读文件，返回文件文本信息，默认编码UTF-8
	 * @param path 文件路径 C:\xnx3.txt
	 * @return String 读取的文件文本信息
	 */
	public static String read(String path){
		return read(path,ENCODE);
	}
	
	/**
	 * 读文件，返回文件文本信息
	 * @param path 文件路径 C:\xnx3.txt
	 * @param encode 文件编码.如 FileUtil.GBK
	 * @return String 返回的文件文本信息
	 */
	public static String read(String path,String encode){
		File file=new File(path);
		return read(file, encode);
	}
	
	/**
	 * 读文件，返回文件内容
	 * @param file 要读取的文件
	 * @param encode 编码，如FileUtil.GBK
	 * @return String 读取的文件文本信息
	 */
	public static String read(File file,String encode){
		StringBuffer xnx3_content=new StringBuffer();
		try{
			BufferedReader xnx3_reader=new BufferedReader(new InputStreamReader(new FileInputStream(file),encode));
			String date=null;
			boolean isReadyAdd = false; //已经加入过了
			while((date=xnx3_reader.readLine())!=null){
				if(isReadyAdd) {
					xnx3_content.append("\n");
				}
				xnx3_content.append(date);
				isReadyAdd = true;
			}
			xnx3_reader.close();
		}catch (Exception e) {
		}
		
		return xnx3_content.toString();
	}
	
	/**
	 * 读文件，读取文件的最后几行，这里可以针对超大文件读
	 * @param filePath 文件路径，传入如 /mnt/tcdn/log/tcdn.log
	 * @param line 要去取最后的几行。 如果文件行数不够，那就有几行取几行
	 * @return 文件结果。其中 list.get(list.size()) 是最后一行 
	 * @throws IOException
	 */
	public static List<String> read(String filePath, int line) throws IOException {
        List<String> List = new ArrayList<>();
        BufferedReader reader = new BufferedReader(new FileReader(filePath));
        String str;
        while ((str = reader.readLine())!= null) {
        	List.add(str);
            if (List.size() > line) {
            	List.remove(0);
            }
        }
        reader.close();
        return List;
    }
	
	/**
	 * 写文件
	 * @param path 传入要保存至的路径————如D:\\a.txt
	 * @param xnx3_content 传入要保存的内容
	 * @return 成功true；失败false
	 */
	public static boolean write(String path,String xnx3_content){
		try {
			FileWriter fw=new FileWriter(path);
			java.io.PrintWriter pw=new java.io.PrintWriter(fw);
			pw.print(xnx3_content);
			pw.close();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
	

	/**
	 * 写文件
	 * @param path 传入要保存至的路径————如D:\\a.txt
	 * @param xnx3_content 传入要保存的内容
	 * @param encode 写出文件的编码
	 * 			<ul>	
	 *				<li>{@link FileUtil#UTF8}</li>
	 * 				<li>{@link FileUtil#GBK}</li>
	 * 			</ul>
	 * @throws IOException  IO异常
	 */
	public static void write(String path,String xnx3_content,String encode) throws IOException{
        FileOutputStream fos = new FileOutputStream(path); 
        OutputStreamWriter osw = new OutputStreamWriter(fos, encode); 
        osw.write(xnx3_content); 
        osw.flush(); 
	}
	
	/**
	 * 写文件
	 * @param file 传入要保存至的路径————如D:\\a.txt
	 * @param xnx3_content 传入要保存的内容
	 * @return 成功、失败
	 */
	public static boolean write(File file,String xnx3_content){
		return write(file.getPath(), xnx3_content);
	}
	
	/**
	 * InputStream转为文件并保存，为jar包内的资源导出而写
	 * <pre>
	 * 	FileUtil.inputStreamToFile(getClass().getResourceAsStream("dm.dll"), "C:\\dm.dll");
	 * </pre>
	 * @param inputStream 输入流
	 * @param targetFilePath 要保存的文件路径
	 */
	public static void inputStreamToFile(InputStream inputStream, String targetFilePath) {
		File file = new File(targetFilePath);
		OutputStream os = null;

		try {
			os = new FileOutputStream(file);
			int bytesRead = 0;
			byte[] buffer = new byte[8192];
			while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			
			os.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try {
				os.close();
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 *  复制文件
	 *  <pre>copyFile("E:\\a.txt", "E:\\aa.txt");</pre>
	 * @param sourceFile 源文件，要复制的文件所在路径
	 * @param targetFile 复制到那个地方
	 */
    public static void copyFile(String sourceFile, String targetFile){
        BufferedInputStream inBuff = null;
        BufferedOutputStream outBuff = null;
        try {
            // 新建文件输入流并对它进行缓冲
            inBuff = new BufferedInputStream(new FileInputStream(sourceFile));

            // 新建文件输出流并对它进行缓冲
            outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));

            // 缓冲数组
            byte[] b = new byte[1024 * 5];
            int len;
            while ((len = inBuff.read(b)) != -1) {
                outBuff.write(b, 0, len);
            }
            // 刷新此缓冲的输出流
            outBuff.flush();
        }catch (Exception e) {
			e.printStackTrace();
		}finally {
			 // 关闭流
            if (inBuff != null)
				try {
					inBuff.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
            if (outBuff != null)
				try {
					outBuff.close();
				} catch (IOException e) {
					e.printStackTrace();
				}  
        }
    }

    /**
	 * 删除文件或目录，它会自动判断，如果传入文件，删除的就是文件，如果是目录，删除的就是目录
	 * @param path 文件路径。如E:\\a\\b.txt 则是删除这个具体的文件，传入如  /mnt/abc/ 则是删除这个目录 （包含 abc 这个目录本身，也就是 /mnt 下就不再有 abc 这个文件夹了）
	 */
    public static void delete(String path){
    	java.io.File f=new java.io.File(path);
    	if(f.exists()){
			if(f.isDirectory()) {
				deleteDirectory(f);
			}else {
				f.exists();
			}
		}
    }
    
	
	/**
	 * 删除单个文件，java操作
	 * @param filePath 文件路径。如E:\\a\\b.txt
	 * @return boolean true：删除成功
	 */
	public static boolean deleteFile(String filePath){
		boolean xnx3_result=false;
		
		java.io.File f=new java.io.File(filePath);
		if(f.isFile()&&f.exists()){
			f.delete();
			xnx3_result=true;
		}
		
		return xnx3_result;
	}
	
	/**
	 * 删除目录。这个目录下的文件，连同这个目录都会被删除
	 * @param directory 传入如 new File("/mnt/tomcat8/webapps/");
	 */
	public static void deleteDirectory(File directory) {
        if (directory.exists()) {
            File[] files = directory.listFiles();
            if (files!= null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        deleteDirectory(file);
                    } else {
                        file.delete();
                    }
                }
            }
            directory.delete();
        }
    }
	
	/**
	 * 传入绝对路径，判断该文件是否存在
	 * @param filePath 文件的绝对路径，如 "C:\\WINDOWS\\system32\\msvcr100.dll"
	 * @return Boolean true:存在
	 */
    public static boolean exists(String filePath){
    	java.io.File f = new java.io.File(filePath);
    	return f.exists();
    }


	/**
	 * 通过网址获得文件长度
	 * @param url 文件的链接地址
	 * @return 文件长度(Hander里的Content-Length)。失败返回-1
	 */
	public static long getFileSize(String url) {
		int nFileLength = -1;
		try {
			URL xnx3_url = new URL(url);
			HttpURLConnection httpConnection = (HttpURLConnection) xnx3_url
					.openConnection();
			httpConnection
					.setRequestProperty("User-Agent", "Internet Explorer");

			int responseCode = httpConnection.getResponseCode();
			if (responseCode >= 400) {
				System.err.println("Error Code : " + responseCode);
				return -2; // -2 represent access is error
			}
			String sHeader;
			for (int i = 1;; i++) {
				sHeader = httpConnection.getHeaderFieldKey(i);
				if (sHeader != null) {
					if (sHeader.equals("Content-Length")) {
						nFileLength = Integer.parseInt(httpConnection
								.getHeaderField(sHeader));
						break;
					}
				} else
					break;
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return nFileLength;
	}

	/**
	 * 从互联网下载文件。适用于http、https协议
	 * <p>下载过程会阻塞当前线程</p>
	 * <p>若文件存在，会先删除存在的文件，再下载</p>
	 * @param downUrl 下载的目标文件网址 如 "http://www.xnx3.com/down/java/j2se_util.zip"
	 * @param savePath 下载的文件保存路径。如 "C:\\test\\j2se_util.zip"
	 * @throws IOException IO异常
	 */
	public static void downFile(String downUrl,String savePath) throws IOException{
		downFile(downUrl, savePath, null);
	}
	

	/**
	 * 从互联网下载文件。适用于http、https协议
	 * <p>下载过程会阻塞当前线程</p>
	 * <p>若文件存在，会先删除存在的文件，再下载</p>
	 * @param downUrl 下载的目标文件网址 如 "http://www.xnx3.com/down/java/j2se_util.zip"
	 * @param savePath 下载的文件保存路径。如 "C:\\test\\j2se_util.zip"
	 * @param param 包含在请求头中的一些参数。比如 User-Agent 等。若为空，则不传递任何参数。例如：
	 * 			<ul>
	 * 				<li>key:User-Agent &nbsp;&nbsp;&nbsp;&nbsp; value: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36</li>
	 * 				<li>key:Host &nbsp;&nbsp;&nbsp;&nbsp; value:xnx3.com</li>
	 * 			</ul>
	 * @throws IOException IO异常
	 */
	public static void downFile(String downUrl,String savePath, Map<String, String> param) throws IOException{
		//默认超时是30秒
		downFile(downUrl, savePath, param, 30000);
	}
	
	/**
	 * 从互联网下载文件。适用于http、https协议
	 * <p>下载过程会阻塞当前线程</p>
	 * <p>若文件存在，会先删除存在的文件，再下载</p>
	 * @param downUrl 下载的目标文件网址 如 "http://www.xnx3.com/down/java/j2se_util.zip"
	 * @param savePath 下载的文件保存路径。如 "C:\\test\\j2se_util.zip"
	 * @param param 包含在请求头中的一些参数。比如 User-Agent 等。若为空，则不传递任何参数。例如：
	 * 			<ul>
	 * 				<li>key:User-Agent &nbsp;&nbsp;&nbsp;&nbsp; value: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36</li>
	 * 				<li>key:Host &nbsp;&nbsp;&nbsp;&nbsp; value:xnx3.com</li>
	 * 			</ul>
	 * @param timeout 超时时间，单位毫秒
	 * @throws IOException IO异常
	 */
	public static void downFile(String downUrl,String savePath, Map<String, String> param, int timeout) throws IOException{
		//判断文件是否已存在，若存在，则先删除
		if(exists(savePath)){
			FileUtil.deleteFile(savePath);
		}
		
		int nStartPos = 0;
		int nRead = 0;
		
		URL url = new URL(downUrl);
		if(downUrl.indexOf("http://") > -1){
			// 打开连接
			HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
			httpConnection.setConnectTimeout(timeout);	//超时30秒
			httpConnection.setReadTimeout(timeout);
			
			// 获得文件长度
			long nEndPos = getFileSize(downUrl);
			
			RandomAccessFile oSavedFile = new RandomAccessFile(savePath, "rw");
			if(param != null){
				for (Map.Entry<String, String> entry : param.entrySet()) {
					httpConnection.setRequestProperty(entry.getKey(), entry.getValue());
				}
			}else{
				httpConnection.setRequestProperty("User-Agent", "Internet Explorer");
			}
			String sProperty = "bytes=" + nStartPos + "-";
			// 告诉服务器book.rar这个文件从nStartPos字节开始传
			httpConnection.setRequestProperty("RANGE", sProperty);
			InputStream input = httpConnection.getInputStream();
			if(nEndPos == -1){
				//没有取得长度字节数，那么就直接将其保存就好了
				oSavedFile.write(inputstreamToByte(input));
			}else{
				byte[] b = new byte[1024];
				// 读取网络文件,写入指定的文件中
				while ((nRead = input.read(b, 0, 1024)) > 0 && nStartPos < nEndPos) {
					oSavedFile.write(b, 0, nRead);
					nStartPos += nRead;
				}
			}
			
			httpConnection.disconnect();
			oSavedFile.close();
		}else if(downUrl.indexOf("https://") > -1){
			HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
			conn.setConnectTimeout(timeout);
			conn.setReadTimeout(timeout);
			
			SSLContext sc = null;
			try {
				sc = SSLContext.getInstance("SSL");
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			}
	        try {
				sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
			} catch (KeyManagementException e) {
				e.printStackTrace();
			}
	        conn.setSSLSocketFactory(sc.getSocketFactory());
	        conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
	        if(param != null){
				for (Map.Entry<String, String> entry : param.entrySet()) {
					conn.setRequestProperty(entry.getKey(), entry.getValue());
				}
			}else{
				conn.setRequestProperty("User-Agent", "Internet Explorer");
			}
	        
	        //文件的长度
	        int nEndPos = Lang.stringToInt(conn.getHeaderField("Content-Length"), -1);

	        RandomAccessFile oSavedFile = new RandomAccessFile(savePath, "rw");
//	        if(param != null){
//				for (Map.Entry<String, String> entry : param.entrySet()) {
//					conn.setRequestProperty(entry.getKey(), entry.getValue());
//				}
//			}else{
//				conn.setRequestProperty("User-Agent", "Internet Explorer");
//			}
			String sProperty = "bytes=" + nStartPos + "-";
			InputStream input = conn.getInputStream();
			
			if(nEndPos == -1){
				//没有取得长度字节数，那么就直接将其保存就好了
				oSavedFile.write(inputstreamToByte(input));
			}else{
				byte[] b = new byte[1024];
				// 读取网络文件,写入指定的文件中
				while ((nRead = input.read(b, 0, 1024)) > 0 && nStartPos < nEndPos) {
					oSavedFile.write(b, 0, nRead);
					nStartPos += nRead;
				}
			}
			
			conn.disconnect();
			oSavedFile.close();
		}
		
	}
	
	/**
	 * 将 {@link BufferedReader} 转换为 {@link String}
	 * @param br 传入{@link BufferedReader}
	 * @return String 若失败，返回 ""
	 */
	public static String bufferedReaderToString(BufferedReader br) {
		String inputLine;
		String str = "";
		try {
			while ((inputLine = br.readLine()) != null) {
				str += inputLine;
			}
			br.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return str;
	}
	
	/**
	 * 将 {@link InputStream} 转化为 byte[]
	 * @param input 传入的 {@link InputStream}
	 * @return 转化为的结果
	 * @throws IOException IO异常
	 */
	public static byte[] inputstreamToByte(InputStream input) throws IOException {
	    ByteArrayOutputStream output = new ByteArrayOutputStream();
	    byte[] buffer = new byte[4096];
	    int n = 0;
	    while (-1 != (n = input.read(buffer))) {
	        output.write(buffer, 0, n);
	    }
	    return output.toByteArray();
	}
	
	/**
	 * 输入文件路径，返回这个文件的创建时间
	 * @param filePath 要获取创建时间的文件的路径，绝对路径
	 * @return 此文件创建的时间
	 */
	public static Date getCreateTime(String filePath){  
		Path path=Paths.get(filePath);    
		BasicFileAttributeView basicview=Files.getFileAttributeView(path, BasicFileAttributeView.class,LinkOption.NOFOLLOW_LINKS );  
		BasicFileAttributes attr;  
		try {
			attr = basicview.readAttributes();  
			Date createDate = new Date(attr.creationTime().toMillis());  
			return createDate;  
		} catch (Exception e) {  
			e.printStackTrace();  
		}
		Calendar cal = Calendar.getInstance();  
		cal.set(1970, 0, 1, 0, 0, 0);  
		return cal.getTime();  
	}
	
	/**
	 * 传入一个文件，从这个文件中找指定后缀的文件返回。
	 * <p>寻找的文件只是此文件一级路径下的，并不是便利它路径下的路径的路径。。。</p>
	 * <p>例如要搜索 H:\git\xnx3_util\target  路径下 后缀是jar的文件</p>
	 * @param file 要搜索的文件，传入如 new File("/images/")
	 * @param suffix 后缀名，传入如 jar
	 * @return 搜索到的文件列表。如果没搜到，则list.size是0
	 */
	public static List<File> findSuffix(File file, String suffix) {
		List<File> list = new ArrayList<File>();	//符合条件的要返回的list
		
		File[] subFiles = file.listFiles();
		for (int i = 0; i < subFiles.length; i++) {
			File subFile = subFiles[i];
			if(subFile.getName() != null) {
				String thisSuffix = Lang.findFileSuffix(subFile.getName());
				if(thisSuffix != null) {
					if(thisSuffix.equalsIgnoreCase(suffix)) {
						list.add(subFile);
					}
				}
			}
		}
		
		return list;
	}
	
	/**
	 * 对某个文本文件后面继续追加文本，此种方式适用于大文件
	 * @param file 要追加到的文件，追加文本到哪个文件
	 * @param text 要追加到文件最后的文本。例如这里可以传入 "\n你好"，便是在最后再插入一行。换行用 \n
	 */
	public static void appendText(File file, String text) {
		if(!file.exists()) {
			//创建目录
			createFolder(file.getPath());
			
			//创建文件
			boolean createNewFile = false;
			try {
				createNewFile = file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
			if(!createNewFile) {
				//创建失败
				return;
			}
			boolean result = write(file, "");
			if(!result) {
				Log.error("自动创建日志文件失败："+file.getPath());
				return;
			}
		}
		BufferedWriter out = null;
		try {
			out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
			out.write(text);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 目录创建，如果存在不做任何处理。若不存在，则自动创建目录。适用于使用本地磁盘进行存储，在本身tomcat中创建目录.有一下两种情况:
	 * <ul>
	 * 		<li>在线上的tomcat项目中，创建的目录是在 tomcat/webapps/ROOT/ 目录下</li>
	 * 		<li>在开发环境Eclipse中，创建的目录是在 target/classes/ 目录下</li>
	 * </ul>
	 * @param path 要检测的目录，相对路径，如 jar/file/  创建到file文件，末尾一定加/     或者jar/file/a.jar创建到file文件夹
	 */
	public static void createFolder(String path){
		if(path == null){
			return;
		}
		
		//windows取的路径是\，所以要将\替换为/
		if(path.indexOf("\\") > 1){
			path = StringUtil.replaceAll(path, "\\\\", "/");
		}
		
		if(path.length() - path.lastIndexOf("/") > 1){
			//path最后是带了具体文件名的，把具体文件名过滤掉，只留文件/结尾
			path = path.substring(0, path.lastIndexOf("/")+1);
		}
		
		//如果目录或文件不存在，再进行创建目录的判断
		if(!FileUtil.exists(path)){
			String[] ps = path.split("/");
			
			String xiangdui = "/";
			//length-1，/最后面应该就是文件名了，所以要忽略最后一个
			for (int i = 0; i < ps.length; i++) {
				if(ps[i].length() > 0){
					xiangdui = xiangdui + ps[i]+"/";
					if(!FileUtil.exists(xiangdui)){
						File file = new File(xiangdui);
						file.mkdir();
					}
				}
			}
		}
	}

	
	public static void main(String[] args) throws IOException {
//		FileUtil.downFile("https://pic1.semaobf1.com/20220520/8CD9C099FD49F54C/8CD9C099FD49F54C.jpg", "I:/video/titlepic/1.jpg");;
		FileUtil.appendText(new File("D:/1/2/3.log"), "111");
	}

}
